// Flags: --expose-internals
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

'use strict';

const common = require('../common');
const assert = require('assert');
const { inspect } = require('util');
const { internalBinding } = require('internal/test/binding');
const a = assert;

// Disable colored output to prevent color codes from breaking assertion
// message comparisons. This should only be an issue when process.stdout
// is a TTY.
if (process.stdout.isTTY)
  process.env.NODE_DISABLE_COLORS = '1';

const strictEqualMessageStart = 'Expected values to be strictly equal:\n';
const start = 'Expected values to be strictly deep-equal:';
const actExp = '+ actual - expected';

assert.ok(a.AssertionError.prototype instanceof Error,
          'a.AssertionError instanceof Error');

assert.throws(() => a(false), a.AssertionError, 'ok(false)');
assert.throws(() => a.ok(false), a.AssertionError, 'ok(false)');

// Throw message if the message is instanceof Error.
{
  let threw = false;
  try {
    assert.ok(false, new Error('ok(false)'));
  } catch (e) {
    threw = true;
    assert.ok(e instanceof Error);
  }
  assert.ok(threw, 'Error: ok(false)');
}


a(true);
a('test', 'ok(\'test\')');
a.ok(true);
a.ok('test');

assert.throws(() => a.equal(true, false),
              a.AssertionError, 'equal(true, false)');

a.equal(null, null);
a.equal(undefined, undefined);
a.equal(null, undefined);
a.equal(true, true);
a.equal(2, '2');
a.notEqual(true, false);

assert.throws(() => a.notEqual(true, true),
              a.AssertionError, 'notEqual(true, true)');

assert.throws(() => a.strictEqual(2, '2'),
              a.AssertionError, 'strictEqual(2, \'2\')');

/* eslint-disable no-restricted-syntax */
assert.throws(() => a.strictEqual(null, undefined),
              a.AssertionError, 'strictEqual(null, undefined)');

assert.throws(
  () => a.notStrictEqual(2, 2),
  {
    message: 'Expected "actual" to be strictly unequal to: 2',
    name: 'AssertionError'
  }
);

assert.throws(
  () => a.notStrictEqual('a '.repeat(30), 'a '.repeat(30)),
  {
    message: 'Expected "actual" to be strictly unequal to:\n\n' +
             `'${'a '.repeat(30)}'`,
    name: 'AssertionError'
  }
);

assert.throws(
  () => a.notEqual(1, 1),
  {
    message: '1 != 1',
    operator: '!='
  }
);

a.notStrictEqual(2, '2');

// Testing the throwing.
function thrower(errorConstructor) {
  throw new errorConstructor({});
}

// The basic calls work.
assert.throws(() => thrower(a.AssertionError), a.AssertionError, 'message');
assert.throws(() => thrower(a.AssertionError), a.AssertionError);
assert.throws(() => thrower(a.AssertionError));

// If not passing an error, catch all.
assert.throws(() => thrower(TypeError));

// When passing a type, only catch errors of the appropriate type.
{
  let threw = false;
  try {
    a.throws(() => thrower(TypeError), a.AssertionError);
  } catch (e) {
    threw = true;
    assert.ok(e instanceof TypeError, 'type');
  }
  assert.ok(threw, 'a.throws with an explicit error is eating extra errors');
}

// doesNotThrow should pass through all errors.
{
  let threw = false;
  try {
    a.doesNotThrow(() => thrower(TypeError), a.AssertionError);
  } catch (e) {
    threw = true;
    assert.ok(e instanceof TypeError);
  }
  assert(threw, 'a.doesNotThrow with an explicit error is eating extra errors');
}

// Key difference is that throwing our correct error makes an assertion error.
{
  let threw = false;
  try {
    a.doesNotThrow(() => thrower(TypeError), TypeError);
  } catch (e) {
    threw = true;
    assert.ok(e instanceof a.AssertionError);
    assert.ok(!e.stack.includes('at Function.doesNotThrow'));
  }
  assert.ok(threw, 'a.doesNotThrow is not catching type matching errors');
}

assert.throws(
  () => a.doesNotThrow(() => thrower(Error), 'user message'),
  {
    name: 'AssertionError',
    code: 'ERR_ASSERTION',
    operator: 'doesNotThrow',
    message: 'Got unwanted exception: user message\n' +
             'Actual message: "[object Object]"'
  }
);

assert.throws(
  () => a.doesNotThrow(() => thrower(Error)),
  {
    code: 'ERR_ASSERTION',
    message: 'Got unwanted exception.\nActual message: "[object Object]"'
  }
);

assert.throws(
  () => a.doesNotThrow(() => thrower(Error), /\[[a-z]{6}\s[A-z]{6}\]/g, 'user message'),
  {
    name: 'AssertionError',
    code: 'ERR_ASSERTION',
    operator: 'doesNotThrow',
    message: 'Got unwanted exception: user message\n' +
             'Actual message: "[object Object]"'
  }
);

// Make sure that validating using constructor really works.
{
  let threw = false;
  try {
    assert.throws(
      () => {
        throw ({}); // eslint-disable-line no-throw-literal
      },
      Array
    );
  } catch {
    threw = true;
  }
  assert.ok(threw, 'wrong constructor validation');
}

// Use a RegExp to validate the error message.
{
  a.throws(() => thrower(TypeError), /\[object Object\]/);

  const symbol = Symbol('foo');
  a.throws(() => {
    throw symbol;
  }, /foo/);

  a.throws(() => {
    a.throws(() => {
      throw symbol;
    }, /abc/);
  }, {
    message: 'The input did not match the regular expression /abc/. ' +
             "Input:\n\n'Symbol(foo)'\n",
    code: 'ERR_ASSERTION',
    operator: 'throws',
    actual: symbol,
    expected: /abc/
  });
}

// Use a fn to validate the error object.
a.throws(() => thrower(TypeError), (err) => {
  if ((err instanceof TypeError) && /\[object Object\]/.test(err)) {
    return true;
  }
});

// https://github.com/nodejs/node/issues/3188
{
  let threw = false;
  let AnotherErrorType;
  try {
    const ES6Error = class extends Error {};
    AnotherErrorType = class extends Error {};

    assert.throws(() => { throw new AnotherErrorType('foo'); }, ES6Error);
  } catch (e) {
    threw = true;
    assert(e instanceof AnotherErrorType,
           `expected AnotherErrorType, received ${e}`);
  }

  assert.ok(threw);
}

// Check messages from assert.throws().
{
  const noop = () => {};
  assert.throws(
    () => { a.throws((noop)); },
    {
      code: 'ERR_ASSERTION',
      message: 'Missing expected exception.',
      operator: 'throws',
      actual: undefined,
      expected: undefined
    });

  assert.throws(
    () => { a.throws(noop, TypeError); },
    {
      code: 'ERR_ASSERTION',
      message: 'Missing expected exception (TypeError).',
      actual: undefined,
      expected: TypeError
    });

  assert.throws(
    () => { a.throws(noop, 'fhqwhgads'); },
    {
      code: 'ERR_ASSERTION',
      message: 'Missing expected exception: fhqwhgads',
      actual: undefined,
      expected: undefined
    });

  assert.throws(
    () => { a.throws(noop, TypeError, 'fhqwhgads'); },
    {
      code: 'ERR_ASSERTION',
      message: 'Missing expected exception (TypeError): fhqwhgads',
      actual: undefined,
      expected: TypeError
    });

  let threw = false;
  try {
    a.throws(noop);
  } catch (e) {
    threw = true;
    assert.ok(e instanceof a.AssertionError);
    assert.ok(!e.stack.includes('at Function.throws'));
  }
  assert.ok(threw);
}

const circular = { y: 1 };
circular.x = circular;

function testAssertionMessage(actual, expected, msg) {
  assert.throws(
    () => assert.strictEqual(actual, ''),
    {
      generatedMessage: true,
      message: msg || strictEqualMessageStart +
               `+ actual - expected\n\n+ ${expected}\n- ''`
    }
  );
}

function testShortAssertionMessage(actual, expected) {
  testAssertionMessage(actual, expected, strictEqualMessageStart +
                                         `\n${inspect(actual)} !== ''\n`);
}

testShortAssertionMessage(null, 'null');
testShortAssertionMessage(true, 'true');
testShortAssertionMessage(false, 'false');
testShortAssertionMessage(100, '100');
testShortAssertionMessage(NaN, 'NaN');
testShortAssertionMessage(Infinity, 'Infinity');
testShortAssertionMessage('a', '"a"');
testShortAssertionMessage('foo', '\'foo\'');
testShortAssertionMessage(0, '0');
testShortAssertionMessage(Symbol(), 'Symbol()');
testShortAssertionMessage(undefined, 'undefined');
testShortAssertionMessage(-Infinity, '-Infinity');
testShortAssertionMessage(function() {}, '[Function]');
testAssertionMessage([], '[]');
testAssertionMessage(/a/, '/a/');
testAssertionMessage(/abc/gim, '/abc/gim');
testAssertionMessage({}, '{}');
testAssertionMessage([1, 2, 3], '[\n+   1,\n+   2,\n+   3\n+ ]');
testAssertionMessage(function f() {}, '[Function: f]');
testAssertionMessage(circular,
                     '{\n+   x: [Circular],\n+   y: 1\n+ }');
testAssertionMessage({ a: undefined, b: null },
                     '{\n+   a: undefined,\n+   b: null\n+ }');
testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
                     '{\n+   a: NaN,\n+   b: Infinity,\n+   c: -Infinity\n+ }');

// https://github.com/nodejs/node-v0.x-archive/issues/5292
assert.throws(
  () => assert.strictEqual(1, 2),
  {
    message: `${strictEqualMessageStart}\n1 !== 2\n`,
    generatedMessage: true
  }
);

assert.throws(
  () => assert.strictEqual(1, 2, 'oh no'),
  {
    message: 'oh no',
    generatedMessage: false
  }
);

{
  let threw = false;
  const rangeError = new RangeError('my range');

  // Verify custom errors.
  try {
    assert.strictEqual(1, 2, rangeError);
  } catch (e) {
    assert.strictEqual(e, rangeError);
    threw = true;
    assert.ok(e instanceof RangeError, 'Incorrect error type thrown');
  }
  assert.ok(threw);
  threw = false;

  // Verify AssertionError is the result from doesNotThrow with custom Error.
  try {
    a.doesNotThrow(() => {
      throw new TypeError('wrong type');
    }, TypeError, rangeError);
  } catch (e) {
    threw = true;
    assert.ok(e.message.includes(rangeError.message));
    assert.ok(e instanceof assert.AssertionError);
    assert.ok(!e.stack.includes('doesNotThrow'), e);
  }
  assert.ok(threw);
}

{
  // Verify that throws() and doesNotThrow() throw on non-functions.
  const testBlockTypeError = (method, fn) => {
    assert.throws(
      () => method(fn),
      {
        code: 'ERR_INVALID_ARG_TYPE',
        name: 'TypeError',
        message: 'The "fn" argument must be of type function.' +
                 common.invalidArgTypeHelper(fn)
      }
    );
  };

  testBlockTypeError(assert.throws, 'string');
  testBlockTypeError(assert.doesNotThrow, 'string');
  testBlockTypeError(assert.throws, 1);
  testBlockTypeError(assert.doesNotThrow, 1);
  testBlockTypeError(assert.throws, true);
  testBlockTypeError(assert.doesNotThrow, true);
  testBlockTypeError(assert.throws, false);
  testBlockTypeError(assert.doesNotThrow, false);
  testBlockTypeError(assert.throws, []);
  testBlockTypeError(assert.doesNotThrow, []);
  testBlockTypeError(assert.throws, {});
  testBlockTypeError(assert.doesNotThrow, {});
  testBlockTypeError(assert.throws, /foo/);
  testBlockTypeError(assert.doesNotThrow, /foo/);
  testBlockTypeError(assert.throws, null);
  testBlockTypeError(assert.doesNotThrow, null);
  testBlockTypeError(assert.throws, undefined);
  testBlockTypeError(assert.doesNotThrow, undefined);
}

// https://github.com/nodejs/node/issues/3275
// eslint-disable-next-line no-throw-literal
assert.throws(() => { throw 'error'; }, (err) => err === 'error');
assert.throws(() => { throw new Error(); }, (err) => err instanceof Error);

// Long values should be truncated for display.
assert.throws(() => {
  assert.strictEqual('A'.repeat(1000), '');
}, (err) => {
  assert.strictEqual(err.code, 'ERR_ASSERTION');
  assert.strictEqual(err.message,
                     `${strictEqualMessageStart}+ actual - expected\n\n` +
                     `+ '${'A'.repeat(1000)}'\n- ''`);
  assert.strictEqual(err.actual.length, 1000);
  assert.ok(inspect(err).includes(`actual: '${'A'.repeat(488)}...'`));
  return true;
});

// Output that extends beyond 10 lines should also be truncated for display.
{
  const multilineString = 'fhqwhgads\n'.repeat(15);
  assert.throws(() => {
    assert.strictEqual(multilineString, '');
  }, (err) => {
    assert.strictEqual(err.code, 'ERR_ASSERTION');
    assert.strictEqual(err.message.split('\n').length, 19);
    assert.strictEqual(err.actual.split('\n').length, 16);
    assert.ok(inspect(err).includes(
      "actual: 'fhqwhgads\\n' +\n" +
      "    'fhqwhgads\\n' +\n".repeat(9) +
      "    '...'"));
    return true;
  });
}

{
  // Bad args to AssertionError constructor should throw TypeError.
  const args = [1, true, false, '', null, Infinity, Symbol('test'), undefined];
  args.forEach((input) => {
    assert.throws(
      () => new assert.AssertionError(input),
      {
        code: 'ERR_INVALID_ARG_TYPE',
        name: 'TypeError',
        message: 'The "options" argument must be of type object.' +
                 common.invalidArgTypeHelper(input)
      });
  });
}

assert.throws(
  () => assert.strictEqual(new Error('foo'), new Error('foobar')),
  {
    code: 'ERR_ASSERTION',
    name: 'AssertionError',
    message: 'Expected "actual" to be reference-equal to "expected":\n' +
             '+ actual - expected\n\n' +
             '+ [Error: foo]\n- [Error: foobar]'
  }
);

// Test strict assert.
{
  const a = require('assert');
  const assert = require('assert').strict;
  /* eslint-disable no-restricted-properties */
  assert.throws(() => assert.equal(1, true), assert.AssertionError);
  assert.notEqual(0, false);
  assert.throws(() => assert.deepEqual(1, true), assert.AssertionError);
  assert.notDeepEqual(0, false);
  assert.equal(assert.strict, assert.strict.strict);
  assert.equal(assert.equal, assert.strictEqual);
  assert.equal(assert.deepEqual, assert.deepStrictEqual);
  assert.equal(assert.notEqual, assert.notStrictEqual);
  assert.equal(assert.notDeepEqual, assert.notDeepStrictEqual);
  assert.equal(Object.keys(assert).length, Object.keys(a).length);
  assert(7);
  assert.throws(
    () => assert(...[]),
    {
      message: 'No value argument passed to `assert.ok()`',
      name: 'AssertionError',
      generatedMessage: true
    }
  );
  assert.throws(
    () => a(),
    {
      message: 'No value argument passed to `assert.ok()`',
      name: 'AssertionError'
    }
  );

  // Test setting the limit to zero and that assert.strict works properly.
  const tmpLimit = Error.stackTraceLimit;
  Error.stackTraceLimit = 0;
  assert.throws(
    () => {
      assert.ok(
        typeof 123 === 'string'
      );
    },
    {
      code: 'ERR_ASSERTION',
      constructor: assert.AssertionError,
      message: 'The expression evaluated to a falsy value:\n\n  ' +
               "assert.ok(\n    typeof 123 === 'string'\n  )\n"
    }
  );
  Error.stackTraceLimit = tmpLimit;

  // Test error diffs.
  let message = [
    start,
    `${actExp} ... Lines skipped`,
    '',
    '  [',
    '    [',
    '      [',
    '        1,',
    '        2,',
    '+       3',
    "-       '3'",
    '      ]',
    '...',
    '    4,',
    '    5',
    '  ]'].join('\n');
  assert.throws(
    () => assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]),
    { message });

  message = [
    start,
    `${actExp} ... Lines skipped`,
    '',
    '  [',
    '    1,',
    '...',
    '    1,',
    '    0,',
    '-   1,',
    '    1,',
    '...',
    '    1,',
    '    1',
    '  ]'
  ].join('\n');
  assert.throws(
    () => assert.deepEqual(
      [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
      [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]),
    { message });

  message = [
    start,
    `${actExp} ... Lines skipped`,
    '',
    '  [',
    '    1,',
    '...',
    '    1,',
    '    0,',
    '+   1,',
    '    1,',
    '    1,',
    '    1',
    '  ]'
  ].join('\n');
  assert.throws(
    () => assert.deepEqual(
      [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
      [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]),
    { message });

  message = [
    start,
    actExp,
    '',
    '  [',
    '    1,',
    '+   2,',
    '-   1,',
    '    1,',
    '    1,',
    '    0,',
    '+   1,',
    '    1',
    '  ]'
  ].join('\n');
  assert.throws(
    () => assert.deepEqual(
      [1, 2, 1, 1, 0, 1, 1],
      [1, 1, 1, 1, 0, 1]),
    { message });

  message = [
    start,
    actExp,
    '',
    '+ [',
    '+   1,',
    '+   2,',
    '+   1',
    '+ ]',
    '- undefined',
  ].join('\n');
  assert.throws(
    () => assert.deepEqual([1, 2, 1], undefined),
    { message });

  message = [
    start,
    actExp,
    '',
    '  [',
    '+   1,',
    '    2,',
    '    1',
    '  ]'
  ].join('\n');
  assert.throws(
    () => assert.deepEqual([1, 2, 1], [2, 1]),
    { message });

  message = `${start}\n` +
    `${actExp} ... Lines skipped\n` +
    '\n' +
    '  [\n' +
    '+   1,\n'.repeat(25) +
    '...\n' +
    '-   2,\n'.repeat(25) +
    '...';
  assert.throws(
    () => assert.deepEqual(Array(28).fill(1), Array(28).fill(2)),
    { message });

  const obj1 = {};
  const obj2 = { loop: 'forever' };
  obj2[inspect.custom] = () => '{}';
  // No infinite loop and no custom inspect.
  assert.throws(() => assert.deepEqual(obj1, obj2), {
    message: `${start}\n` +
    `${actExp}\n` +
    '\n' +
    '+ {}\n' +
    '- {\n' +
    '-   [Symbol(nodejs.util.inspect.custom)]: [Function],\n' +
    "-   loop: 'forever'\n" +
    '- }'
  });

  // notDeepEqual tests
  assert.throws(
    () => assert.notDeepEqual([1], [1]),
    {
      message: 'Expected "actual" not to be strictly deep-equal to:\n\n' +
               '[\n  1\n]\n'
    }
  );

  message = 'Expected "actual" not to be strictly deep-equal to:' +
            `\n\n[${'\n  1,'.repeat(45)}\n...\n`;
  const data = Array(51).fill(1);
  assert.throws(
    () => assert.notDeepEqual(data, data),
    { message });
  /* eslint-enable no-restricted-properties */
}

assert.throws(
  () => assert.ok(null),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    generatedMessage: true,
    message: 'The expression evaluated to a falsy value:\n\n  ' +
             'assert.ok(null)\n'
  }
);
assert.throws(
  () => assert(typeof 123n === 'string'),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    generatedMessage: true,
    message: 'The expression evaluated to a falsy value:\n\n  ' +
             "assert(typeof 123n === 'string')\n"
  }
);

assert.throws(
  () => assert(false, Symbol('foo')),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    generatedMessage: false,
    message: 'Symbol(foo)'
  }
);

{
  // Test caching.
  const fs = internalBinding('fs');
  const tmp = fs.close;
  fs.close = common.mustCall(tmp, 1);
  function throwErr() {
    assert(
      (Buffer.from('test') instanceof Error)
    );
  }
  assert.throws(
    () => throwErr(),
    {
      code: 'ERR_ASSERTION',
      constructor: assert.AssertionError,
      message: 'The expression evaluated to a falsy value:\n\n  ' +
               "assert(\n    (Buffer.from('test') instanceof Error)\n  )\n"
    }
  );
  assert.throws(
    () => throwErr(),
    {
      code: 'ERR_ASSERTION',
      constructor: assert.AssertionError,
      message: 'The expression evaluated to a falsy value:\n\n  ' +
               "assert(\n    (Buffer.from('test') instanceof Error)\n  )\n"
    }
  );
  fs.close = tmp;
}

assert.throws(
  () => {
    a(
      (() => 'string')()
      // eslint-disable-next-line operator-linebreak
      ===
      123 instanceof
          Buffer
    );
  },
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n' +
             '  a(\n' +
             '    (() => \'string\')()\n' +
             '    // eslint-disable-next-line operator-linebreak\n' +
             '    ===\n' +
             '    123 instanceof\n' +
             '        Buffer\n' +
             '  )\n'
  }
);

assert.throws(
  () => {
    a(
      (() => 'string')()
      // eslint-disable-next-line operator-linebreak
      ===
  123 instanceof
          Buffer
    );
  },
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n' +
             '  a(\n' +
             '    (() => \'string\')()\n' +
             '    // eslint-disable-next-line operator-linebreak\n' +
             '    ===\n' +
             '  123 instanceof\n' +
             '        Buffer\n' +
             '  )\n'
  }
);

/* eslint-disable indent */
assert.throws(() => {
a((
  () => 'string')() ===
123 instanceof
Buffer
);
}, {
  code: 'ERR_ASSERTION',
  constructor: assert.AssertionError,
  message: 'The expression evaluated to a falsy value:\n\n' +
           '  a((\n' +
           '    () => \'string\')() ===\n' +
           '  123 instanceof\n' +
           '  Buffer\n' +
           '  )\n'
  }
);
/* eslint-enable indent */

assert.throws(
  () => {
    assert(true); assert(null, undefined);
  },
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n  ' +
             'assert(null, undefined)\n'
  }
);

assert.throws(
  () => {
    assert
     .ok(null, undefined);
  },
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n  ' +
             'ok(null, undefined)\n'
  }
);

assert.throws(
  // eslint-disable-next-line dot-notation, quotes
  () => assert['ok']["apply"](null, [0]),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n  ' +
             'assert[\'ok\']["apply"](null, [0])\n'
  }
);

assert.throws(
  () => {
    const wrapper = (fn, value) => fn(value);
    wrapper(assert, false);
  },
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n  fn(value)\n'
  }
);

assert.throws(
  () => assert.ok.call(null, 0),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n  ' +
             'assert.ok.call(null, 0)\n',
    generatedMessage: true
  }
);

assert.throws(
  () => assert.ok.call(null, 0, 'test'),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'test',
    generatedMessage: false
  }
);

// Works in eval.
assert.throws(
  () => new Function('assert', 'assert(1 === 2);')(assert),
  {
    code: 'ERR_ASSERTION',
    constructor: assert.AssertionError,
    message: 'The expression evaluated to a falsy value:\n\n  assert(1 === 2)\n'
  }
);
assert.throws(
  () => eval('console.log("FOO");\nassert.ok(1 === 2);'),
  {
    code: 'ERR_ASSERTION',
    message: 'false == true'
  }
);

assert.throws(
  () => assert.throws(() => {}, 'Error message', 'message'),
  {
    code: 'ERR_INVALID_ARG_TYPE',
    name: 'TypeError',
    message: 'The "error" argument must be of type function or ' +
             'an instance of Error, RegExp, or Object. Received type string ' +
             "('Error message')"
  }
);

[
  1,
  false,
  Symbol()
].forEach((input) => {
  assert.throws(
    () => assert.throws(() => {}, input),
    {
      code: 'ERR_INVALID_ARG_TYPE',
      message: 'The "error" argument must be of type function or ' +
               'an instance of Error, RegExp, or Object.' +
               common.invalidArgTypeHelper(input)
    }
  );
});

{

  assert.throws(() => {
    assert.ok((() => Boolean('' === false))());
  }, {
    message: 'The expression evaluated to a falsy value:\n\n' +
             "  assert.ok((() => Boolean('\\u0001' === false))())\n"
  });

  const errFn = () => {
    const err = new TypeError('Wrong value');
    err.code = 404;
    throw err;
  };
  const errObj = {
    name: 'TypeError',
    message: 'Wrong value'
  };
  assert.throws(errFn, errObj);

  errObj.code = 404;
  assert.throws(errFn, errObj);

  // Fail in case a expected property is undefined and not existent on the
  // error.
  errObj.foo = undefined;
  assert.throws(
    () => assert.throws(errFn, errObj),
    {
      code: 'ERR_ASSERTION',
      name: 'AssertionError',
      message: `${start}\n${actExp}\n\n` +
               '  Comparison {\n' +
               '    code: 404,\n' +
               '-   foo: undefined,\n' +
               "    message: 'Wrong value',\n" +
               "    name: 'TypeError'\n" +
               '  }'
    }
  );

  // Show multiple wrong properties at the same time.
  errObj.code = '404';
  assert.throws(
    () => assert.throws(errFn, errObj),
    {
      code: 'ERR_ASSERTION',
      name: 'AssertionError',
      message: `${start}\n${actExp}\n\n` +
               '  Comparison {\n' +
               '+   code: 404,\n' +
               "-   code: '404',\n" +
               '-   foo: undefined,\n' +
               "    message: 'Wrong value',\n" +
               "    name: 'TypeError'\n" +
               '  }'
    }
  );

  assert.throws(
    () => assert.throws(() => { throw new Error(); }, { foo: 'bar' }, 'foobar'),
    {
      constructor: assert.AssertionError,
      code: 'ERR_ASSERTION',
      message: 'foobar'
    }
  );

  assert.throws(
    () => a.doesNotThrow(() => { throw new Error(); }, { foo: 'bar' }),
    {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message: 'The "expected" argument must be of type function or an ' +
               'instance of RegExp. Received an instance of Object'
    }
  );

  assert.throws(() => { throw new Error('e'); }, new Error('e'));
  assert.throws(
    () => assert.throws(() => { throw new TypeError('e'); }, new Error('e')),
    {
      name: 'AssertionError',
      code: 'ERR_ASSERTION',
      message: `${start}\n${actExp}\n\n` +
               '  Comparison {\n' +
               "    message: 'e',\n" +
               "+   name: 'TypeError'\n" +
               "-   name: 'Error'\n" +
               '  }'
    }
  );
  assert.throws(
    () => assert.throws(() => { throw new Error('foo'); }, new Error('')),
    {
      name: 'AssertionError',
      code: 'ERR_ASSERTION',
      generatedMessage: true,
      message: `${start}\n${actExp}\n\n` +
               '  Comparison {\n' +
               "+   message: 'foo',\n" +
               "-   message: '',\n" +
               "    name: 'Error'\n" +
               '  }'
    }
  );

  // eslint-disable-next-line no-throw-literal
  assert.throws(() => { throw undefined; }, /undefined/);
  assert.throws(
    // eslint-disable-next-line no-throw-literal
    () => a.doesNotThrow(() => { throw undefined; }),
    {
      name: 'AssertionError',
      code: 'ERR_ASSERTION',
      message: 'Got unwanted exception.\nActual message: "undefined"'
    }
  );
}

assert.throws(
  () => assert.throws(() => { throw new Error(); }, {}),
  {
    message: "The argument 'error' may not be an empty object. Received {}",
    code: 'ERR_INVALID_ARG_VALUE'
  }
);

assert.throws(
  () => a.throws(
    // eslint-disable-next-line no-throw-literal
    () => { throw 'foo'; },
    'foo'
  ),
  {
    code: 'ERR_AMBIGUOUS_ARGUMENT',
    message: 'The "error/message" argument is ambiguous. ' +
             'The error "foo" is identical to the message.'
  }
);

assert.throws(
  () => a.throws(
    () => { throw new TypeError('foo'); },
    'foo'
  ),
  {
    code: 'ERR_AMBIGUOUS_ARGUMENT',
    message: 'The "error/message" argument is ambiguous. ' +
             'The error message "foo" is identical to the message.'
  }
);
/* eslint-enable no-restricted-syntax */

// Should not throw.
// eslint-disable-next-line no-restricted-syntax, no-throw-literal
assert.throws(() => { throw null; }, 'foo');

assert.throws(
  () => assert.strictEqual([], []),
  {
    message: 'Values have same structure but are not reference-equal:\n\n[]\n'
  }
);

{
  const args = (function() { return arguments; })('a');
  assert.throws(
    () => assert.strictEqual(args, { 0: 'a' }),
    {
      message: 'Expected "actual" to be reference-equal to "expected":\n' +
               '+ actual - expected\n\n' +
               "+ [Arguments] {\n- {\n    '0': 'a'\n  }"
    }
  );
}

assert.throws(
  () => { throw new TypeError('foobar'); },
  {
    message: /foo/,
    name: /^TypeError$/
  }
);

assert.throws(
  () => assert.throws(
    () => { throw new TypeError('foobar'); },
    {
      message: /fooa/,
      name: /^TypeError$/
    }
  ),
  {
    message: `${start}\n${actExp}\n\n` +
             '  Comparison {\n' +
             "+   message: 'foobar',\n" +
             '-   message: /fooa/,\n' +
             "    name: 'TypeError'\n" +
             '  }'
  }
);

{
  let actual = null;
  const expected = { message: 'foo' };
  assert.throws(
    () => assert.throws(
      () => { throw actual; },
      expected
    ),
    {
      operator: 'throws',
      actual,
      expected,
      generatedMessage: true,
      message: `${start}\n${actExp}\n\n` +
              '+ null\n' +
              '- {\n' +
              "-   message: 'foo'\n" +
              '- }'
    }
  );

  actual = 'foobar';
  const message = 'message';
  assert.throws(
    () => assert.throws(
      () => { throw actual; },
      { message: 'foobar' },
      message
    ),
    {
      actual,
      message,
      operator: 'throws',
      generatedMessage: false
    }
  );
}

// Indicate where the strings diverge.
assert.throws(
  () => assert.strictEqual('test test', 'test foobar'),
  {
    code: 'ERR_ASSERTION',
    name: 'AssertionError',
    message: strictEqualMessageStart +
             '+ actual - expected\n\n' +
             "+ 'test test'\n" +
             "- 'test foobar'\n" +
             '        ^'
  }
);

// Check for reference-equal objects in `notStrictEqual()`
assert.throws(
  () => {
    const obj = {};
    assert.notStrictEqual(obj, obj);
  },
  {
    code: 'ERR_ASSERTION',
    name: 'AssertionError',
    message: 'Expected "actual" not to be reference-equal to "expected": {}'
  }
);

assert.throws(
  () => {
    const obj = { a: true };
    assert.notStrictEqual(obj, obj);
  },
  {
    code: 'ERR_ASSERTION',
    name: 'AssertionError',
    message: 'Expected "actual" not to be reference-equal to "expected":\n\n' +
             '{\n  a: true\n}\n'
  }
);

{
  let threw = false;
  try {
    assert.deepStrictEqual(Array(100).fill(1), 'foobar');
  } catch (err) {
    threw = true;
    assert(/actual: \[Array],\n  expected: 'foobar',/.test(inspect(err)));
  }
  assert(threw);
}

assert.throws(
  () => a.equal(1),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.deepEqual(/a/),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.notEqual(null),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.notDeepEqual('test'),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.strictEqual({}),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.deepStrictEqual(Symbol()),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.notStrictEqual(5n),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.notDeepStrictEqual(undefined),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.strictEqual(),
  { code: 'ERR_MISSING_ARGS' }
);

assert.throws(
  () => a.deepStrictEqual(),
  { code: 'ERR_MISSING_ARGS' }
);

// Verify that `stackStartFunction` works as alternative to `stackStartFn`.
{
  (function hidden() {
    const err = new assert.AssertionError({
      actual: 'foo',
      operator: 'strictEqual',
      stackStartFunction: hidden
    });
    const err2 = new assert.AssertionError({
      actual: 'foo',
      operator: 'strictEqual',
      stackStartFn: hidden
    });
    assert(!err.stack.includes('hidden'));
    assert(!err2.stack.includes('hidden'));
  })();
}

// Multiple assert.match() tests.
{
  assert.throws(
    () => assert.match(/abc/, 'string'),
    {
      code: 'ERR_INVALID_ARG_TYPE',
      message: 'The "regexp" argument must be an instance of RegExp. ' +
               "Received type string ('string')"
    }
  );
  assert.throws(
    () => assert.match('string', /abc/),
    {
      actual: 'string',
      expected: /abc/,
      operator: 'match',
      message: 'The input did not match the regular expression /abc/. ' +
               "Input:\n\n'string'\n",
      generatedMessage: true
    }
  );
  assert.throws(
    () => assert.match('string', /abc/, 'foobar'),
    {
      actual: 'string',
      expected: /abc/,
      operator: 'match',
      message: 'foobar',
      generatedMessage: false
    }
  );
  const errorMessage = new RangeError('foobar');
  assert.throws(
    () => assert.match('string', /abc/, errorMessage),
    errorMessage
  );
  assert.throws(
    () => assert.match({ abc: 123 }, /abc/),
    {
      actual: { abc: 123 },
      expected: /abc/,
      operator: 'match',
      message: 'The "string" argument must be of type string. ' +
               'Received type object ({ abc: 123 })',
      generatedMessage: true
    }
  );
  assert.match('I will pass', /pass$/);
}

// Multiple assert.doesNotMatch() tests.
{
  assert.throws(
    () => assert.doesNotMatch(/abc/, 'string'),
    {
      code: 'ERR_INVALID_ARG_TYPE',
      message: 'The "regexp" argument must be an instance of RegExp. ' +
               "Received type string ('string')"
    }
  );
  assert.throws(
    () => assert.doesNotMatch('string', /string/),
    {
      actual: 'string',
      expected: /string/,
      operator: 'doesNotMatch',
      message: 'The input was expected to not match the regular expression ' +
               "/string/. Input:\n\n'string'\n",
      generatedMessage: true
    }
  );
  assert.throws(
    () => assert.doesNotMatch('string', /string/, 'foobar'),
    {
      actual: 'string',
      expected: /string/,
      operator: 'doesNotMatch',
      message: 'foobar',
      generatedMessage: false
    }
  );
  const errorMessage = new RangeError('foobar');
  assert.throws(
    () => assert.doesNotMatch('string', /string/, errorMessage),
    errorMessage
  );
  assert.throws(
    () => assert.doesNotMatch({ abc: 123 }, /abc/),
    {
      actual: { abc: 123 },
      expected: /abc/,
      operator: 'doesNotMatch',
      message: 'The "string" argument must be of type string. ' +
               'Received type object ({ abc: 123 })',
      generatedMessage: true
    }
  );
  assert.doesNotMatch('I will pass', /different$/);
}

{
  const tempColor = inspect.defaultOptions.colors;
  assert.throws(() => {
    inspect.defaultOptions.colors = true;
    // Guarantee the position indicator is placed correctly.
    assert.strictEqual(111554n, 11111115);
  }, (err) => {
    assert.strictEqual(inspect(err).split('\n')[5], '     ^');
    inspect.defaultOptions.colors = tempColor;
    return true;
  });
}
